﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Common.JWT
{
    public class JsonWebToken : IJsonToken
    {
        private IJsonSerializer JsonSerializer { get; set; }

        private readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);

        private readonly IDictionary<JwtHashAlgorithm, Func<byte[], byte[], byte[]>> HashAlgorithms;

        public JsonWebToken(IJsonSerializer jsonSerializer)
        {
            JsonSerializer = jsonSerializer;
            HashAlgorithms = JwtHelper.GetHashAlgorithms();
        }

        #region 公共方法
        /// <summary>
        /// jwt编码
        /// </summary>
        /// <typeparam name="T">数据类型</typeparam>
        /// <param name="payload">数据</param>
        /// <param name="key">加密Key</param>
        /// <param name="algorithm">加密算法类型</param>
        /// <returns></returns>
        public string Encode<T>(T payload, string key, JwtHashAlgorithm algorithm = JwtHashAlgorithm.HS256)
        {
            return Encode(new Dictionary<string, string>(), payload, Encoding.UTF8.GetBytes(key), algorithm);
        }

        /// <summary>
        /// jwt编码
        /// </summary>
        /// <typeparam name="T">数据类型</typeparam>
        /// <param name="extraHeaders">头部</param>
        /// <param name="payload">数据</param>
        /// <param name="key">加密Key</param>
        /// <param name="algorithm">加密算法类型</param>
        /// <returns></returns>
        public string Encode<T>(IDictionary<string, string> extraHeaders, T payload, string key, JwtHashAlgorithm algorithm = JwtHashAlgorithm.HS256)
        {
            return Encode(extraHeaders, payload, Encoding.UTF8.GetBytes(key), algorithm);
        }


        /// <summary>
        /// jwt解码
        /// </summary>
        /// <typeparam name="T">数据类型</typeparam>
        /// <param name="token">jwt-token</param>
        /// <param name="key">加密Key</param>
        /// <returns></returns>
        public T Decode<T>(string token, string key)
        {
            var parts = token.Split('.');
            if (parts.Length != 3)
            {
                throw new ArgumentException("Token格式不对");
            }
            var payload = parts[1];
            var payloadJson = Encoding.UTF8.GetString(JwtHelper.Base64UrlDecode(payload));
            var decodedCrypto = Convert.ToBase64String(JwtHelper.Base64UrlDecode(parts[2]));
            if (Verify(payload, payloadJson, parts[0], decodedCrypto, key))
            {
                try
                {
                    return JsonSerializer.Deserialize<T>(payloadJson);
                }
                catch (FormatException)
                {
                    throw new FormatException("payloadJson不正确.");
                }
            }
            return default(T);
        }

        /// <summary>
        /// 验证
        /// </summary>
        /// <param name="payload">数据</param>
        /// <param name="payloadJson">数据json格式</param>
        /// <param name="header">Base64-safeurl编码的头部信息</param>
        /// <param name="decodedCrypto">Base64编码的签名</param>
        /// <param name="key">加密key</param>
        /// <returns></returns>
        public bool Verify(string payload, string payloadJson, string header, string decodedCrypto, string key)
        {
            var headerJson = Encoding.UTF8.GetString(JwtHelper.Base64UrlDecode(header));
            var headerData = JsonSerializer.Deserialize<Dictionary<string, string>>(headerJson);
            var algorithm = (string)headerData["alg"];
            var bytesToSign = Encoding.UTF8.GetBytes(string.Concat(header, ".", payload));
            var signatureData = HashAlgorithms[JwtHelper.GetHashAlgorithm(algorithm)](Encoding.UTF8.GetBytes(key), bytesToSign);
            var decodedSignature = Convert.ToBase64String(signatureData);

            return Verify(payloadJson, decodedCrypto, decodedSignature);
        }

        public bool IsDataExp(string header)
        {
            var headerJson = Encoding.UTF8.GetString(JwtHelper.Base64UrlDecode(header));
            var headerData = JsonSerializer.Deserialize<Dictionary<string, string>>(headerJson);
            string expObj = string.Empty;
            //exp是数据设置的过期时间
            if (!headerData.TryGetValue("exp", out expObj) || string.IsNullOrEmpty(expObj))
            {
                return false;
            }
            int expInt;
            try
            {
                expInt = Convert.ToInt32(expObj);
            }
            catch (FormatException)
            {
                throw new Exception("Claim 'exp' 必须是正整数.");
            }
            var secondsSinceEpoch = Math.Round((DateTime.UtcNow - UnixEpoch).TotalSeconds);
            if (secondsSinceEpoch >= expInt)
            {
                return false;
            }
            return true;
        }
        #endregion

        #region 私有方法
        /// <summary>
        /// jwt编码
        /// </summary>
        /// <typeparam name="T">数据类型</typeparam>
        /// <param name="extraHeaders">扩展头信息</param>
        /// <param name="payload">数据</param>
        /// <param name="key">加密</param>
        /// <param name="algorithm">加密类型</param>
        /// <returns></returns>
        private string Encode<T>(IDictionary<string, string> extraHeaders, T payload, byte[] key, JwtHashAlgorithm algorithm = JwtHashAlgorithm.HS256)
        {
            var segments = new List<string>();
            var header = new Dictionary<string, string>(extraHeaders)
            {
                { "typ", "JWT" },
                { "alg", algorithm.ToString() }
            };

            var headerBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(header));
            var payloadBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(payload));

            segments.Add(JwtHelper.Base64UrlEncode(headerBytes));
            segments.Add(JwtHelper.Base64UrlEncode(payloadBytes));

            var stringToSign = string.Join(".", segments.ToArray());
            var bytesToSign = Encoding.UTF8.GetBytes(stringToSign);

            var signature = HashAlgorithms[algorithm](key, bytesToSign);
            segments.Add(JwtHelper.Base64UrlEncode(signature));

            return string.Join(".", segments.ToArray());
        }

        /// <summary>
        /// 验证
        /// </summary>
        /// <param name="payloadJson">数据json</param>
        /// <param name="decodedCrypto">decode签名</param>
        /// <param name="decodedSignature">数据签名</param>
        /// <returns></returns>
        private bool Verify(string payloadJson, string decodedCrypto, string decodedSignature)
        {
            if (decodedCrypto != decodedSignature)
            {
                throw new Exception("Invalid signature:crypto:" + decodedCrypto + ",sign:" + decodedSignature);
            }
            return true;
           
        }

        #endregion
    }
}
